package com.atguigu.service.impl;

import com.atguigu.entity.BaseBrand;
import com.atguigu.exception.SleepUtils;
import com.atguigu.mapper.BaseBrandMapper;
import com.atguigu.service.BaseBrandService;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 品牌表 服务实现类
 * </p>
 *
 * @author zhangqiang
 * @since 2022-08-26
 */
@Service
public class BaseBrandServiceImpl extends ServiceImpl<BaseBrandMapper, BaseBrand> implements BaseBrandService {

    @Autowired
    public RedisTemplate redisTemplate;

    //@Override
    public synchronized void setNum00() {
        String value = (String) redisTemplate.opsForValue().get("num");
        if (StringUtils.isEmpty(value)) {
            redisTemplate.opsForValue().set("num", "1");
        } else {
            int num = Integer.parseInt(value);
            redisTemplate.opsForValue().set("num", String.valueOf(++num));
        }
    }

    //分布式锁方案1
    //@Override
    public void setNum01() {
        //利用redis的setnx命令,当设置的key不存在的时候 setnx会生效
        Boolean acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", "ok");
        if (acquireLock) {
            //拿到锁 执行业务
            doBusiness();
            //业务做完删除锁
            redisTemplate.delete("lock");
        } else {
            //如果没有拿到锁
            setNum();
        }
    }

    //分布式锁方案2
    //@Override
    public void setNum02() {
        //利用redis的setnx命令,当设置的key不存在的时候 setnx会生效
        Boolean acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", "ok", 3, TimeUnit.SECONDS);
        if (acquireLock) {
            //拿到锁 执行业务
            doBusiness();
            //业务做完删除锁
            redisTemplate.delete("lock");
        } else {
            //如果没有拿到锁
            setNum();
        }
    }

    //分布式锁方案3
    //@Override
    public void setNum03() {
        String token = UUID.randomUUID().toString();
        //利用redis的setnx命令,当设置的key不存在的时候 setnx会生效
        Boolean acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", token, 3, TimeUnit.SECONDS);
        if (acquireLock) {
            //拿到锁 执行业务
            doBusiness();
            //业务做完删除锁
            String lockToken = (String) redisTemplate.opsForValue().get("lock");
            //判断lock是否为一个lock，是则删除
            if (token.equals(lockToken)) {
                redisTemplate.delete("lock");
            }
        } else {
            //如果没有拿到锁
            setNum();
        }
    }

    //分布式锁方案4
    //@Override
    public void setNum04() {
        String token = UUID.randomUUID().toString();
        //利用redis的setnx命令,当设置的key不存在的时候 setnx会生效
        Boolean acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", token, 3, TimeUnit.SECONDS);
        if (acquireLock) {
            //拿到锁 执行业务
            doBusiness();
            //业务做完删除锁
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            //把lua脚本封装到redisScript中
            redisScript.setScriptText(luaScript);
            //设置脚本运行完成后 返回的数据类型
            redisScript.setResultType(Long.class);
            //执行脚本
            redisTemplate.execute(redisScript, Arrays.asList("lock"), token);

            /* 上面的脚本已经做了如下判断
            String lockToken = (String) redisTemplate.opsForValue().get("lock");
            //判断lock是否为一个lock，是则删除
            if (token.equals(lockToken)) {
                redisTemplate.delete("lock");
            }
            */
        } else {
            //如果没有拿到锁
            setNum();
        }
    }

    //分布式锁优化1
    //@Override
    public void setNum2() {
        String token = UUID.randomUUID().toString();
        //利用redis的setnx命令,当设置的key不存在的时候 setnx会生效
        Boolean acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", token, 300, TimeUnit.SECONDS);
        if (acquireLock) {
            //拿到锁 执行业务
            doBusiness();
            //业务做完删除锁
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            //把lua脚本封装到redisScript中
            redisScript.setScriptText(luaScript);
            //设置脚本运行完成后 返回的数据类型
            redisScript.setResultType(Long.class);
            //执行脚本
            redisTemplate.execute(redisScript, Arrays.asList("lock"), token);

            /* 上面的脚本已经做了如下判断
            String lockToken = (String) redisTemplate.opsForValue().get("lock");
            //判断lock是否为一个lock，是则删除
            if (token.equals(lockToken)) {
                redisTemplate.delete("lock");
            }
            */
        } else {
            //如果没有拿到锁
            while (true) {
                SleepUtils.millis(50);
                Boolean retryAcquireLock = redisTemplate.opsForValue().setIfAbsent("lock", token, 300, TimeUnit.SECONDS);
                if (retryAcquireLock) {
                    break;
                }
            }
            setNum();
        }
    }

    //分布式锁优化2-重入锁
    Map<Thread, Boolean> threadMap = new HashMap<>();
    //@Override
    public void setNum3() {
        Boolean flag = threadMap.get(Thread.currentThread());
        String token = null;
        boolean acquireLock = false;
        if (flag == null || flag == false) {
            token = UUID.randomUUID().toString();
            //利用redis的setnx命令,当设置的key不存在的时候 setnx会生效
            acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", token, 300, TimeUnit.SECONDS);
        } else {
            //代表已经拿到锁
            String lock = (String) redisTemplate.opsForValue().get("lock");
            //token赋值
            token=lock;
            acquireLock=true;

        }

        if (acquireLock) {
            //拿到锁 执行业务
            doBusiness();
            //业务做完删除锁
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            //把lua脚本封装到redisScript中
            redisScript.setScriptText(luaScript);
            //设置脚本运行完成后 返回的数据类型
            redisScript.setResultType(Long.class);
            //执行脚本
            redisTemplate.execute(redisScript, Arrays.asList("lock"), token);
            /* 上面的脚本已经做了如下判断
            String lockToken = (String) redisTemplate.opsForValue().get("lock");
            //判断lock是否为一个lock，是则删除
            if (token.equals(lockToken)) {
                redisTemplate.delete("lock");
            }
            */
        } else {
            //如果没有拿到锁
            while (true) {
                SleepUtils.millis(50);
                boolean retryAcquireLock = redisTemplate.opsForValue().setIfAbsent("lock", token, 300, TimeUnit.SECONDS);
                if (retryAcquireLock) {
                    //拿到锁后就不需要自旋，把锁标记一下
                    threadMap.put(Thread.currentThread(), true);
                    break;
                }
            }
            setNum();
        }
    }

    //分布式锁优化3-重入锁
    Map<Thread, String> threadMap1 = new HashMap<>();
    //@Override
    public void setNum4() {
        String token = threadMap1.get(Thread.currentThread());
        boolean acquireLock = false;
        if (token==null) {
            token = UUID.randomUUID().toString();
            //利用redis的setnx命令,当设置的key不存在的时候 setnx会生效
            acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", token, 300, TimeUnit.SECONDS);
        } else {
            //代表已经拿到锁
            //String lock = (String) redisTemplate.opsForValue().get("lock");
            //token赋值
            //token=lock;
            acquireLock=true;
        }

        if (acquireLock) {
            //拿到锁 执行业务
            doBusiness();
            //业务做完删除锁
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            //把lua脚本封装到redisScript中
            redisScript.setScriptText(luaScript);
            //设置脚本运行完成后 返回的数据类型
            redisScript.setResultType(Long.class);
            //执行脚本
            redisTemplate.execute(redisScript, Arrays.asList("lock"), token);
            /* 上面的脚本已经做了如下判断
            String lockToken = (String) redisTemplate.opsForValue().get("lock");
            //判断lock是否为一个lock，是则删除
            if (token.equals(lockToken)) {
                redisTemplate.delete("lock");
            }
            */
        } else {
            //如果没有拿到锁
            while (true) {
                SleepUtils.millis(50);
                boolean retryAcquireLock = redisTemplate.opsForValue().setIfAbsent("lock", token, 300, TimeUnit.SECONDS);
                if (retryAcquireLock) {
                    //拿到锁后就不需要自旋，把锁标记一下
                    threadMap1.put(Thread.currentThread(), token);
                    break;
                }
            }
            setNum();
        }
    }

    //分布式锁优化3-重入锁
    ThreadLocal<String>threadLocal=new ThreadLocal<>();
    //@Override
    public void setNum5() {
        String token = threadLocal.get();
        boolean acquireLock = false;
        if (token==null) {
            token = UUID.randomUUID().toString();
            //利用redis的setnx命令,当设置的key不存在的时候 setnx会生效
            acquireLock = redisTemplate.opsForValue().setIfAbsent("lock", token, 300, TimeUnit.SECONDS);
        } else {
            //代表已经拿到锁
            //String lock = (String) redisTemplate.opsForValue().get("lock");
            //token赋值
            //token=lock;
            acquireLock=true;
        }

        if (acquireLock) {
            //拿到锁 执行业务
            doBusiness();
            //业务做完删除锁
            String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            //把lua脚本封装到redisScript中
            redisScript.setScriptText(luaScript);
            //设置脚本运行完成后 返回的数据类型
            redisScript.setResultType(Long.class);
            //执行脚本
            redisTemplate.execute(redisScript, Arrays.asList("lock"), token);
            //放在内存泄露
            threadLocal.remove();
        } else {
            //如果没有拿到锁
            while (true) {
                SleepUtils.millis(50);
                boolean retryAcquireLock = redisTemplate.opsForValue().setIfAbsent("lock", token, 300, TimeUnit.SECONDS);
                if (retryAcquireLock) {
                    //拿到锁后就不需要自旋，把锁标记一下
                    threadLocal.set(token);
                    break;
                }
            }
            setNum();
        }
    }


    private void doBusiness() {
        String value = (String) redisTemplate.opsForValue().get("num");
        if (StringUtils.isEmpty(value)) {
            redisTemplate.opsForValue().set("num", "1");
        } else {
            int num = Integer.parseInt(value);
            redisTemplate.opsForValue().set("num", String.valueOf(++num));
        }
    }

    @Autowired
    private RedissonClient redissonClient;
    @Override
    public void setNum(){
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        try {
            doBusiness();
        } finally {
            lock.unlock();
        }

    }
}
